// Batch mode SCAN algorithm that runs for different eps values in [0,1]
// mu = 2 by default

package scan;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

public class ScanMain implements Constants {

	private OpenFile openFile;
	private SaveFile saveFile;
	private Network network;
	private SCAN scan;
	private Evaluate evaluate;
	private ScanConvert converter;
	private int direction;
	public static String filename;
	public static String folder;
	// debug == 1: print everything to consoled
	public static final int debug = 0;
	private List<List<HashMap<Integer, HashSet<Integer>>>> cluster;
	HashSet<Integer> largestComponent;
	public static int THRESHOLD = 4;

	public static void main(String[] args) {
		ScanMain main = new ScanMain();
		boolean directed = false;
		boolean weighted = false;

		if (args.length != 3) {
			System.out.println("Three parameters are required:");
			System.out.println("<filename without extension> <d=true/false> <w=true/false>");
			return;
		} else {
			if (((String) args[1]).equals("d=true")) {
				directed = true;
			}

			if (((String) args[2]).equals("w=true")) {
				weighted = true;
			}
			main.run(args[0], directed, weighted, new ArrayList<Integer>(), 4);
		}

	}

	public HashMap<Integer, Integer> run(String file, boolean b_direct, boolean b_weight, ArrayList<Integer> clusterpath, Integer thresh) {
		THRESHOLD = thresh;
		filename = file;
		boolean b_directed = b_direct;
		boolean b_weighted = b_weight;

		// added
		direction = BIDIRECTIONAL;

		network = new Network(b_directed, b_weighted);
		openFile = new OpenFile(network);

		// saveFile = new SaveFile(network);
		// scan = new SCAN(network);
		// evaluate = new Evaluate(network);

		/* open file and load network */
		openFile.openPairsFile(filename + ".pairs");
		largestComponent = getLargestComponent(network);

		Network currentNet, root;
		HashMap<Integer, Network> innerCluster, innerClusterDeep;

		/* create a new network object. this network should be clustered */
		currentNet = new Network(network, false, false, largestComponent);
		/* cluster network and write the results to file */
		root = clusterNet(currentNet);
		saveToFile(root, 1, 1);

		for (int i = 0; i < clusterpath.size(); i++) {
			System.out.println("Cluster again for Cluster Nr. " + clusterpath.get(i));
			Network tmp = new Network(root, clusterpath.get(i));
			Network tmp2 = clusterNet(tmp);
			saveToFile(tmp2, 1, 1);
			root = new Network(tmp2, false, true);
		}
		return getMapping(root);
	}

	/**
	 * 
	 * @param net
	 *            The Network that should be clustered
	 * @return a clustered Network. Not more than THRESHOLD clusters
	 */
	private Network clusterNet(Network net) {
		boolean flatten = false;
		Network prevNet = new Network(net, false, false);
		BestScan tmp;
		Network currentNet;
		boolean once = true;
		ArrayList<Network> nets = new ArrayList<Network>();
		/* do clustering for the first Time, then repeat as long as there are more than THRESHOLD clusters... */
		tmp = new BestScan(net);
		/* iterate SCAN delivers the best run of SCAN, always returning the one with the highest Modularity */
		tmp.iterateSCAN(net.isDirected(), net.isWeighted());
		nets.add(net);

		int i = 0;
		while (nets.get(i).getNumberOfClusters() > THRESHOLD) {
			System.out.println("Cluster again, there are " + nets.get(i).getNumberOfClusters() + " > " + THRESHOLD + " Clusters");

			/* returns a net which is the exact copy of the given network also copying the Network ID */
			prevNet = new Network(nets.get(i), false, true);

			/* add the copy of the Network to the list of nets */
			nets.add(prevNet);

			/* returns a net, which is shrinked using the clusterID from the previousNetwork */
			currentNet = new Network(prevNet, true, false);

			/* do the clustering on the current net */
			tmp = new BestScan(currentNet);
			tmp.iterateSCAN(net.isDirected(), net.isWeighted());

			/* Save the new ClusterIds from currentNet to the prevNet */
			new ScanConvert(prevNet).getFlattenNetwork(currentNet);
			i++;
		}
		return new Network(nets.get(i), false, true);
	}

	/**
	 * This Method prints a given clustering
	 * 
	 * @param clustering
	 *            HashMap that should be printed
	 */
	private void printHashMap(HashMap<Integer, HashSet<Integer>> clustering) {
		Iterator itHash;
		int tmp;
		for (Entry<Integer, HashSet<Integer>> entry : clustering.entrySet()) {
			System.out.print("Cluster " + entry.getKey() + ": ");
			itHash = entry.getValue().iterator();
			while (itHash.hasNext()) {
				tmp = (Integer) itHash.next();
				System.out.print(tmp + ",");
			}
			System.out.println();
		}
	}

	private HashMap<Integer, Integer> getMapping(Network net) {
		HashMap<Integer, Integer> mapping = new HashMap<Integer, Integer>();
		for (Entry<String, Vertex> entry : net.getVertexMap().entrySet()) {
			int vertex = new Integer(entry.getValue().getLabel());
			int clusterID = entry.getValue().getClusterId();
			mapping.put(vertex, clusterID);
		}
		return mapping;
	}

	private void saveToFile(Network net, double eps, double modularity) {
		new SaveFile(net).clustResultsCommaDelimited(ScanMain.filename + "_" + net.isDirected() + "_" + net.isWeighted(), eps, 2, modularity, false);
	}

	private HashSet<Integer> getLargestComponent(Network net) {
		System.out.println("get Largest Component");
		int vertexA_int, vertexB_int, vertex_int;
		String vertexA_s, vertexB_s, vertex_s;
		Vertex vertex;

		Graph graph = new Graph(net.getNumVertices());
		/* mapping Component --> Vertices */
		HashSet<Integer> components;

		/* create Graph in another data structure (we need the neighbors) */
		for (Map.Entry<Integer, Edge> entry2 : net.getEdgeMap().entrySet()) {
			vertexA_s = entry2.getValue().getVertexA();
			vertexB_s = entry2.getValue().getVertexB();
			vertexA_int = new Integer(vertexA_s);
			vertexB_int = new Integer(vertexB_s);
			graph.addEdge(vertexA_int, vertexB_int);
		}

		/* get the connected components */
		ConnectedComponents ccp = new ConnectedComponents(graph);
		System.out.println("Anzahl der Komponenten: " + ccp.getNumberOfComponents());
		int max = 0;
		int index = 0;
		for (int i = 0; i < ccp.getNumberOfComponents(); i++) {
			if (ccp.getComponent(i).size() >= max) {
				index = i;
				max = ccp.getComponent(i).size();
			}
		}
		components = ccp.getComponents(index);
		System.out.println("Largest Component: " + components.toString());
		System.out.println("fetching Largest Component done");
		return components;
	}
}